package com.sl.core.engine.trans;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.*;
import cn.hutool.json.JSONUtil;
import com.sl.core.utils.JsonUtil;
import com.sl.core.engine.ProcessConfig;
import com.sl.core.engine.meta.trans.ProcessTransDescriptor;
import com.sl.core.engine.thread.ThreadConfig;
import com.sl.core.engine.thread.TraceThreadPoolTaskExecutor;
import com.sl.core.engine.utils.BeanUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.hop.core.logging.LogLevel;
import org.slf4j.MDC;

import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;


public class ProcessInstance extends ProcessTrans {

    @Getter
    @Setter
    private String tid;

    @Getter
    @Setter
    private Object inParam;
    @Setter
    @Getter
    private Object outResult;

    public static String TID_IDENTIFY = "tid";

    public static String INSTANCE_IDENTIFY = "instanceId";

    /**
     * 流程版本
     */
    @Getter
    @Setter
    private String processName;
    @Getter
    @Setter
    private String processVersion;


    @Getter
    @Setter
    private String description;
    @Getter
    @Setter
    private String processCode;

    @Getter
    @Setter
    private String processId;


    @Getter
    @Setter
    private String openId;

    @Getter
    @Setter
    private Boolean fastFailFlag = true;

    @Getter
    @Setter
    private Boolean finishEnduranceLogFlag = false;

    @Getter
    @Setter
    private Boolean saveMainLogFlag = true;
    @Getter
    @Setter
    private Boolean saveDetailLogFlag = true;

    @Getter
    @Setter
    private Boolean logDetailZipFlag = true;

    @Getter
    @Setter
    private String logStoreTypeCode;
    @Getter
    @Setter
    private String instanceId;

    private TraceThreadPoolTaskExecutor bootThreadTaskExecutor;


    public ProcessInstance(ProcessTransDescriptor transMeta, ProcessConfig processConfig) {
        this(transMeta, processConfig, null);
    }

    public ProcessInstance(ProcessTransDescriptor transMeta, ProcessConfig processConfig, ProcessInstance processInstance) {
        super(transMeta, processConfig, processInstance);
        if (ObjectUtil.isNull(processConfig)) {
            processConfig = new ProcessConfig();
        }
        this.setTid(processConfig.getTid());
        this.setInstanceId(processConfig.getInstanceId());
        this.setOpenId(processConfig.getOpenId());
        this.setProcessName(processConfig.getProcessName());
        this.setProcessCode(processConfig.getProcessCode());
        this.setProcessVersion(processConfig.getProcessVersion());
        this.setDescription(processConfig.getDescription());
        this.setProcessId(processConfig.getProcessId());
        this.setLogStoreTypeCode(processConfig.getLogStoreTypeCode());
        //设置线程池
        setBooThreadPool(processConfig.getBootThreadCode());

        String logLevelCode = processConfig.getLogLevelCode();
        setLogLevel(logLevelCode);
    }

    private void setBooThreadPool(String bootThreadCode) {
        TraceThreadPoolTaskExecutor taskExecutor = ThreadConfig.INSTANCE.get(bootThreadCode);
        if (ObjectUtil.isNull(taskExecutor)) {
            taskExecutor = DEFAULT_POOL;
        }
        this.bootThreadTaskExecutor = taskExecutor;
    }

    @SneakyThrows
    @Override
    public void prepareExecution() {
        super.prepareExecution();
    }

    @SneakyThrows
    public void execute(Boolean preException) {
        if (ObjectUtil.isNull(preException) || BooleanUtil.isTrue(preException)) {
            this.prepareExecution();
        }

        String[] strings = this.listParameters();
        if (!ArrayUtil.contains(strings, "input")) {
            this.addParameterDefinition("input", "", "入参");
        }
        if (!ArrayUtil.contains(strings, "output")) {
            this.addParameterDefinition("output", "", "出参");
        }
        String str = null;
        try {
            str=JsonUtil.toStr(inParam);
        }catch (Exception e){

        }
        this.setParameterValue("input", str);
        this.setParameterValue("output", "");
        this.setExecutionEndDate(null);
        AtomicBoolean atomicBoolean = BeanUtil.convert(ReflectUtil.getFieldValue(this, "isAlreadyStopped"));
        atomicBoolean.set(false);


        MDC.put(TID_IDENTIFY, this.getTid());
        MDC.put(INSTANCE_IDENTIFY, this.getInstanceId());

        this.startThreads();
    }

    public void genatatorInstanceId() {
        this.instanceId = IdUtil.fastUUID();
    }

    @SneakyThrows
    public void execute(Object inParam, Boolean preException) {
        this.inParam = inParam;
        this.execute(preException);
    }

    @SneakyThrows
    public Object getResultObj() {
        Object struct = this.getOutResult();
        return stringToJson(struct);
    }


    private static Object stringToJson(Object argument) {
        if(ObjectUtil.isNull(argument)){
            return argument;
        }
        if(!(argument instanceof String)){
            return  argument;
        }
        if (StrUtil.isBlank(argument.toString())) {
            return argument;
        }
        if (BooleanUtil.isTrue(JSONUtil.isTypeJSON(argument.toString()))) {
            return JSONUtil.parse(argument);
        } else {
            return argument;
        }
    }

    public void setLogLevel(String logLevelCode) {
        if (StrUtil.isNotBlank(logLevelCode)) {
            LogLevel lv = LogLevel.lookupCode(logLevelCode);
            setLogLevel(lv);
        }
    }

    public Object executeSync(Object param, Boolean preException) {
        return this.execute(param, preException, true, null);
    }

    public Object execute(Object param, Boolean preException, Boolean waiteFlag, Consumer consumer) {
        Future<Object> submit = bootThreadTaskExecutor.submit(() -> {
            this.execute(param, preException);
            this.waitUntilFinished();
            if (ObjectUtil.isNotNull(consumer)) {
                consumer.accept(this);
            }
            Object resultObj = this.getResultObj();
            this.log.logBasic("执行成功");
            return resultObj;
        }, this.getTid());

        Object result = null;
        if (BooleanUtil.isTrue(waiteFlag)) {
            try {
                result = submit.get();
            } catch (Throwable e) {
                this.stopAll();
                this.safeStop();
                log.logError("执行出错", e);
                submit.cancel(true);
                this.callStepStatus();
                throw new RuntimeException(e);
            }
        }
        assertThrowable();

        return result;
    }

    @SneakyThrows
    public void assertThrowable() {
        if (BooleanUtil.isFalse(fastFailFlag)) {
            return;
        }
        List<Throwable> throwableList = getThrowableList();
        if (CollUtil.isEmpty(throwableList)) {
            return;
        }
        Throwable throwable = CollUtil.get(throwableList, 0);
        throw throwable;
    }

    public Object executeAsync(Object param, Consumer consumer) {
        return this.execute(param, true, false, consumer);
    }

    public void kill() {
        bootThreadTaskExecutor.close(this.getTid());
    }

    public Integer isEnd() {
        return this.getErrors() > 0 ? 1 : 2;
    }

    public String getOutResultStr() {
        return outResult.toString();
    }
}

